package com.yhb.service.impl;

import com.yhb.channel.model.IUserIdentify;
import com.yhb.channel.model.IUserInfo;
import com.yhb.channel.provider.IUserIdentifyProvider;
import com.yhb.channel.provider.IUserInfoProvider;
import com.yhb.conf.Configuration;
import com.yhb.conf.ResultCode;
import com.yhb.controller.out.vo.ThirdAuthVo;
import com.yhb.controller.vo.AdminAuthVo;
import com.yhb.controller.vo.AuthVo;
import com.yhb.controller.vo.UserInfoVo;
import com.yhb.dao.*;
import com.yhb.model.AdminUser;
import com.yhb.model.Certificate;
import com.yhb.model.Message;
import com.yhb.model.User;
import com.yhb.service.*;
import com.yhb.third.leshua.DigestUtil;
import com.yhb.utils.DateUtils;
import com.yhb.utils.JWTProvider;
import com.yhb.utils.SignConfig;
import lombok.extern.log4j.Log4j;
import lombok.var;
import me.edzh.spring.bootstrap.interceptor.AuthInterceptor;
import me.edzh.spring.bootstrap.model.IUser;
import me.edzh.spring.bootstrap.model.Tuple;
import me.edzh.spring.bootstrap.resolvers.CurrentUserResolver;
import me.edzh.spring.bootstrap.service.ObjectiveStorageService;
import me.edzh.spring.bootstrap.service.SMSService;
import me.edzh.spring.bootstrap.utils.JSONUtil;
import me.edzh.spring.bootstrap.utils.ListUtils;
import me.edzh.spring.bootstrap.utils.MathUtils;
import me.edzh.spring.bootstrap.utils.StringUtils;
import me.edzh.spring.bootstrap.utils.exception.ServiceException;
import me.edzh.spring.bootstrap.utils.security.Jwt;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.http.util.TextUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Service
@Log4j
public class UserServiceImpl implements UserService, CurrentUserResolver.UserProvider, IUserInfoProvider, IUserIdentifyProvider, AuthInterceptor.ExternalAuthLogic {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AdminUserMapper adminUserMapper;

    @Autowired
    private UserIdentifyMapper userIdentifyMapper;

    @Autowired
    private InviteCodeMapper inviteCodeMapper;

    @Autowired
    private InvitationMapper invitationMapper;

    @Autowired
    private InviteCodeService inviteCodeService;

    @Autowired
    private Jwt jwt;

    @Autowired
    private ObjectiveStorageService ossServiceImpl;

    @Autowired
    private RealNameAuthenticationService realNameAuthenticationService;

    @Autowired
    private SMSService smsService;

    @Autowired
    private UserVipFlowMapper userVipFlowMapper;

    @Lazy
    @Autowired
    private MPosService mPosService;

    @Autowired
    private MessageService messageService;

    @Lazy
    @Autowired
    private CpsService cpsService;

    @Lazy
    @Autowired
    private SplittingService splittingService;


    private final static String KEY_PREFIX = "user-token-";
    private final static String CHANGE_NUMBER_PREFIX = "user-change-mobile-";
    private final static String RESET_PASSWORD_PREFIX = "reset-password-";
    private final static String ADMIN_LOGIN_PREFIX = "admin-login-";

    private final static String THIRD_AUTH_TOKEN_PREFIX = "third-auth-token-";
    private final static String THIRD_CODE_PREFIX = "third-code-";

    @Autowired
    private RedisTemplate<String, String> redis;

    private final static Pattern idCodePattern = Pattern.compile("\\d{8}-(\\d{8}|长期)");


    @Override
    public AuthVo login(String userName, String pwd) {
        User u = getUserByMobilePhone(userName);
        if (u == null) {
            throw new ServiceException(ResultCode.ERROR_DB_CODE, "登录错误");
        }
        String pwdHash = SignConfig.getHash(pwd, u.getId());

        User user = userMapper.login(userName, pwdHash);
        if (user == null) {
            throw new ServiceException(ResultCode.ERROR_DB_CODE, "登录错误");
        }
        String token = jwt.getToken(user);

        redis.boundValueOps(KEY_PREFIX + user.getId()).set(token, JWTProvider.getOptions().expireTime, TimeUnit.MILLISECONDS); // 挤掉登录态

        var cpsUser = cpsService.loginCps(user.getId());

        return AuthVo.builder()
                .user(UserInfoVo.builder().user(user).build())
                .token(token)
                .cpsToken(cpsUser.getCpsToken())
                .build();
    }

    @Override
    public String adminGetVerifyCode(String mobile) {
        var u = adminUserMapper.getUserByMobile(mobile);
        if (u == null) {
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "查无此用户");
        }
        String token = UUID.randomUUID().toString();
        String verifyCode = (int) (MathUtils.random(1., 9.) * 100000) + "";
        var model = User.builder()
                .mobile(mobile)
                .id(u.getId())
                .password(verifyCode)
                .build();

        redis.boundValueOps(ADMIN_LOGIN_PREFIX + token).set(JSONUtil.toJson(model), 10, TimeUnit.MINUTES);
        Map<String, String> p = new HashMap<>();
        p.put("code", verifyCode);
        smsService.sendSms(mobile, "SMS_159530057", p);
        return token;
    }

    @Override
    public AdminAuthVo adminVerify(String token, String verifyCode) {
        String modelString = redis.boundValueOps(ADMIN_LOGIN_PREFIX + token).get();
        if (modelString == null) {
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "验证码无效或已过期");
        }


        var model = JSONUtil.parseJson(modelString, User.class);
        if (!model.getPassword().equals(verifyCode))
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "验证码无效或已过期");
        redis.delete(ADMIN_LOGIN_PREFIX + token);

        String userId = model.getId();
        var u = adminUserMapper.getUserById(userId);

        String loginToken = jwt.getToken(u);
        redis.boundValueOps(KEY_PREFIX + u.getId()).set(loginToken, JWTProvider.getOptions().expireTime, TimeUnit.MILLISECONDS); // 挤掉登录态

        return AdminAuthVo.builder()
                .token(loginToken)
                .user(u)
                .build();
    }

    @Override
    public IUser getUserOfId(String id) {
        log.info("userId: " + id);
        if (StringUtils.isEmpty(id)) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "null user id", new RuntimeException());
        }

        if (id.startsWith("admin")) return getAdminUser(id);
        else return getUser(id);
    }

    @Override
    public User getUser(String id) {

        log.info("userId: " + id);
        if (StringUtils.isEmpty(id)) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "null user id", new RuntimeException());
        }

        User user = userMapper.getUserById(id);
        if (user == null) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "用户不存在");
        }
        return user;
    }

    @Override
    public AdminUser getAdminUser(String id) {
        log.info("userId: " + id);
        if (StringUtils.isEmpty(id)) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "null user id", new RuntimeException());
        }

        AdminUser user = adminUserMapper.getUserById(id);
        if (user == null) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "用户不存在");
        }
        return user;
    }

    @Override
    public User getUserByMobilePhone(String mobilePhone) {
        return userMapper.getUserByMobile(mobilePhone);
    }

    @Override
    public String getInvitorId(String userId) {
        return userMapper.getInvitorId(userId);
    }

    @Override
    public User getInvitor(String userId) {
        return userMapper.getInvitor(userId);
    }

    @Override
    @Transactional()
    public AuthVo register(User user, String inviteCode) {
        User invitor;
        if (!TextUtils.isEmpty(inviteCode)) { // 先简单判空, 后面要查db
            invitor = getUserByInviteCode(inviteCode);
            if (invitor == null) {
                throw new ServiceException(ResultCode.ERROR_INVALID_INVITE_CODE, "邀请码无效");
            }


        } else {
            throw new ServiceException(ResultCode.ERROR_INVALID_INVITE_CODE, "邀请码无效");

        }
        if (userMapper.getUserByMobile(user.getMobile()) != null) {
            throw new ServiceException(ResultCode.ERROR_DUPLICATED_MOBILE, "手机号已被注册");
        }

        user.setCharacter(User.USER);

        String newInviteCode = inviteCodeService.getInviteCode(); // 生成邀请码
        user.setInviteCode(newInviteCode);

        userMapper.addUser(user);
        user.setPassword(SignConfig.getHash(user.getPassword(), user.getId()));
        userMapper.modifyUser(user);

        inviteCodeMapper.addCode(newInviteCode, user.getId()); // 邀请码记录

        // 20200509 干掉邀请送vip活动
//        if (Configuration.getConfig(Configuration.ConfigKey.INVITE_VIP_AVAILA`LE, Boolean.class)) {
//            if (invitor.getVipType() == User.VipType.ORDINARY) {
//                if (getInviteeIds(invitor.getId()).size() == 0) { // 第一次邀请人送 vip 30天
//                    earnVip(invitor, 30);
//                }
//            }
//        }

        invitationMapper.insert(user.getId(), invitor.getId()); // 新增邀请关系

        messageService.addMessage(Message.builder()
                .type(Message.Type.SYSTEM)
                .userId(user.getId())
                .content("欢迎注册[银合宝]，免费使用银合宝购买和分享都可以获得高额收益，VIP可获得更多专属尊贵权益。快开启省＆赚钱模式吧！")
                .title("欢迎注册")
                .build(), false);

        messageService.addMessage(Message.builder()
                .type(Message.Type.SYSTEM)
                .userId(invitor.getId())
                .content("恭喜您成功邀请[" + user.getMobile() + "]加入银合宝, 快带小伙伴开启银合宝创业之旅吧!")
                .title("邀请成功通知")
                .build(), true);


        var cpsUser = cpsService.loginCps(user.getId());

        var token = jwt.getToken(user);
        redis.boundValueOps(KEY_PREFIX + user.getId()).set(token, JWTProvider.getOptions().expireTime, TimeUnit.MILLISECONDS); // 挤掉登录态

        return AuthVo.builder()
                .user(UserInfoVo.builder().user(getUser(user.getId())).build())
                .token(token)
                .cpsToken(cpsUser.getCpsToken())
                .build();
    }

    @Override
    public User getUserByInviteCode(String inviteCode) {
        String userId = inviteCodeMapper.userIdByInviteCode(inviteCode);
        return userMapper.getUserById(userId);
    }

    @Override
    public Long getInviteeCountRegisterAfter(String invitorId, Date time) {
        return userMapper.getInviteeCountRegisterTimeAfter(invitorId, time);
    }

    @Override
    public String getUserNameByUserId(String userId) {
        return StringUtils.getMaskString(getUserNameByUserIdUnmasked(userId));
    }

    @Override
    public String getUserNameByUserIdUnmasked(String userId) {
        User user = getUser(userId);

        Certificate certificate = userIdentifyMapper.getCertificate(user.getId());

        if (certificate == null) return user.getNickname();

        return certificate.getName();
    }

    @Override
    public String getUserNameByInviteCode(String inviteCode) {
        User user = getUserByInviteCode(inviteCode);
        Certificate certificate = userIdentifyMapper.getCertificate(user.getId());

        if (certificate == null) return user.getNickname();

        return StringUtils.getMaskString(certificate.getName());
    }

    @Override
    @Transactional
    public void addCertificateInfo(Certificate certificate, User user) {

        if (TextUtils.isEmpty(certificate.getIdCode())) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "missing id number");
        }

        if (TextUtils.isEmpty(certificate.getName())) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "missing name");
        }

        if (TextUtils.isEmpty(certificate.getValidation())) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "missing validation");
        }

        if (!idCodePattern.matcher(certificate.getValidation()).matches()) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "validation should be yyyyMMdd-yyyyMMdd or yyyyMMdd-长期");

        }

        if (TextUtils.isEmpty(certificate.getAddress())) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "missing address");
        }

        if (TextUtils.isEmpty(certificate.getPhotoUrl())) {
            throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "missing photo");
        }


        if (user.getCertificated() == 1)
            throw new ServiceException(ResultCode.ERROR_ALREADY_CERTIFICATED, "already certificated");

        var c = userIdentifyMapper.getCertificateByIdCode(certificate.getIdCode());
        if (c != null) {
            throw new ServiceException(ResultCode.ERROR_ID_USED, "该身份证已被使用, 请勿重复认证");
        }

        // 调用百度云实名认证接口, 失败会有exception
        certificate.setScore(realNameAuthenticationService.checkRealName(certificate.getName(), certificate.getIdCode(), certificate.getPhotoUrl()));
        certificate.setIdCodeMd5(DigestUtil.getMd5(certificate.getIdCode()));

        userIdentifyMapper.addCertificate(certificate, user.getId());
        int genderBit = Integer.parseInt("" + certificate.getIdCode().charAt(16));
        user.setGender(genderBit % 2 == 1 ? "男" : "女");
        user.setCertificated(1);
        userMapper.modifyUser(user);
        setUserActive(user.getId());

        // splittingService.publishIdentifyBonus(user);

        checkInvitorVIPBonus(user);


    }

    @Override
    @Async
    public void checkInvitorVIPBonus(User invitee) {
        var invitor = getInvitor(invitee.getId());
        if (invitor.getVipSubType() == User.VipSubType.PERMANENT) return;

        var now = new Date();
        // 新注册90天内, 只要推满3人实名, 即发放体验VIP从当天到次月底
        var registerTime = invitor.getRegisterTime();
        var registerDays = DateUtils.daysBetweenToday(registerTime);
        if (registerDays <= 90) {
            var certificatedCount = certificatingFollowerCount(invitor.getId(), registerTime, now);
            if (certificatedCount == 3) {
                var c = Calendar.getInstance();
                c.add(Calendar.MONTH, 1);
                DateUtils.setLastDayOfMonth(c);

                earnVip(invitor, false, c.getTime());
                checkVipValidation(invitor.getId());

                messageService.addMessage(Message.builder()
                        .title("VIP推广发放")
                        .content("恭喜您本期推广达标成为体验VIP会员, 马上购买199商品礼包还可晋级永久VIP, 享受更多权益")
                        .type(Message.Type.SYSTEM)
                        .userId(invitor.getId())
                        .build(), true);
            }
        }



    }

    @Override
    public void checkInvitorVIPBonusMonthly(User invitor, Date start, Date end) {
        if (invitor.getVipSubType() == User.VipSubType.PERMANENT) return;

        // 本月推3人, 发放vip 从下月1号开始到月底
        var certificatedCount = certificatingFollowerCount(invitor.getId(), DateUtils.getFirstDayFirstSecondThisMonth(), DateUtils.getLastDayLastSecondThisMonth());
        if (certificatedCount >= 3) {
            var c = new Calendar.Builder().setInstant(end).build();
            c.add(Calendar.MONTH, 1);


            earnVip(invitor, false, DateUtils.getFirstDayFirstSecondThisMonth(c), DateUtils.getLastDayLastSecondThisMonth(c));
            checkVipValidation(invitor.getId());

            messageService.addMessage(Message.builder()
                    .title("VIP推广发放")
                    .content("恭喜您本期推广达标成为体验VIP会员, 马上购买199商品礼包还可晋级永久VIP, 享受更多权益")
                    .type(Message.Type.SYSTEM)
                    .userId(invitor.getId())
                    .build(), true);
        } else {
            messageService.addMessage(Message.builder()
                    .title("VIP推广")
                    .content("很遗憾, 您本期推广未达标, 降级为普通用户, 马上购199商品礼包可晋级为永久VIP, 享更多权益")
                    .type(Message.Type.SYSTEM)
                    .userId(invitor.getId())
                    .build(), true);
        }

    }

    @Override
    public Certificate getCertificate(String userId) {
        return userIdentifyMapper.getCertificate(userId);
    }

    @Override
    public IUserIdentify getUserIdentify(String userId) {
        Certificate certificate = userIdentifyMapper.getCertificate(userId);
        certificate.setIdAvatarUrl(ossServiceImpl.signUrl(certificate.getIdAvatarUrl()));
        certificate.setIdEmblemUrl(ossServiceImpl.signUrl(certificate.getIdEmblemUrl()));

        return certificate;
    }

    @Override
    public IUserInfo getUserInfo(String userId) {
        return getUser(userId);
    }

    @Override
    public String getHoldCardImgUrl(int gender) {
        switch (gender) {
            case 1:
                return ossServiceImpl.signUrl("https://private.static.ylmvip.cn/holdId/2661574265674_.pic_hd.jpg");
            default:
            case 0:
                return ossServiceImpl.signUrl("https://private.static.ylmvip.cn/holdId/2671574265679_.pic_hd.jpg");
        }
    }

    @Override
    public List<String> getInviteeIds(String userId) {
        var result = userMapper.getInviteeIds(userId);
        return ObjectUtils.defaultIfNull(result, new ArrayList<>());
    }

    @Override
    public List<String> getAllUserId() {
        return userMapper.allUserIds();
    }

    @Override
    public List<User> getAllUser() {
        return userMapper.allUser();
    }

    @Override
    public List<User> searchUser(String key) {
        if (TextUtils.isEmpty(key) || key.length() < 5)
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "key 必须大于5位");
        return userMapper.searchUser(key);
    }

    @Override
    @Transactional
    public void resetCertificate(User user) {
        user.setCertificated(0);
        userIdentifyMapper.removeCertificate(user.getId());
        userMapper.modifyUser(user);

    }

    @Override
    public User userByIdCodeMd5(String md5) {
        var cert = userIdentifyMapper.getCertificateByIdMd5(md5);
        if (cert == null) {
            return null;
        }
        return getUser(cert.getUserId());
    }
//
//    @Override
//    public void updateMd5() {
//        var certs = userIdentifyMapper.getAll();
//        for (var c : certs) {
//            c.setIdCodeMd5(DigestUtil.getMd5(c.getIdCode()));
//            userIdentifyMapper.updateMd5(DigestUtil.getMd5(c.getIdCode()), c.getUserId());
//        }
//    }

    @Override
    public void modifyUser(User user) {
        user.setPassword(null); // 清空密码段, 避免外部逻辑异常导致内部出错, 内部需要改密都在 userMapper.modify
        userMapper.modifyUser(user);
    }

    @Override
    public boolean shouldPass(String token, IUser user) {
        String t = redis.boundValueOps(KEY_PREFIX + user.getId()).get();
        return token.equals(t);
    }

    @Override
    public List<Tuple<User, Long>> getFollowerVipActivationRank(String type, String invitorId) {
        // DAILY / MONTHLY
        Calendar c = Calendar.getInstance();
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);

        switch (type) {
            case "MONTHLY":
                c.set(Calendar.DAY_OF_MONTH, 1);
                break;
            case "TOTAL":
                c.set(Calendar.YEAR, 2000);
                break;

        }

        List<String> followers = userMapper.getInviteeIds(invitorId);
        List<Tuple<User, Long>> result = ListUtils.newList(followers,
                followerId -> {
                    Long followersRegisterToday = userMapper.getInviteeCountRegisterTimeAfter(followerId, c.getTime());
                    User follower = getUser(followerId);
                    return new Tuple<>(follower, followersRegisterToday);
                });

        result.sort((t1, t2) -> t2.getValue2().compareTo(t1.getValue2()));

        return result;
    }


    @Override
    public List<String> getRootUserIds() {
        return userMapper.getRootUsers();
    }


    @Override
    public String getChangePhoneNumberSms(String userId, String newPhoneNumber) {
        var u = getUserByMobilePhone(newPhoneNumber);
        if (u != null) {
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "此手机号已被另一个用户绑定");
        }
        String token = UUID.randomUUID().toString();
        String verifyCode = (int) (MathUtils.random(1., 9.) * 100000) + "";
        var model = User.builder()
                .mobile(newPhoneNumber)
                .build();

        redis.boundValueOps(CHANGE_NUMBER_PREFIX + userId + token).set(JSONUtil.toJson(model), 10, TimeUnit.MINUTES);
        Map<String, String> p = new HashMap<>();
        p.put("code", verifyCode);
        smsService.sendSms(newPhoneNumber, "SMS_159530057", p);
        return token;
    }

    @Override
    public void verifyChangePhoneNumber(String userId, String smsToken, String verifyCode) {
        String modelString = redis.boundValueOps(CHANGE_NUMBER_PREFIX + userId + smsToken).get();
        if (modelString == null) {
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "验证码无效或已过期");
        }

        redis.delete(CHANGE_NUMBER_PREFIX + userId + smsToken);
        var model = JSONUtil.parseJson(modelString, User.class);

        userMapper.modifyUser(User.builder()
                .mobile(model.getMobile())
                .id(userId)
                .build());

    }

    @Override
    public String getResetPasswordSms(String phoneNumber) {
        var u = getUserByMobilePhone(phoneNumber);
        if (u == null) {
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "查无此用户, 请注册");
        }
        String token = UUID.randomUUID().toString();
        String verifyCode = (int) (MathUtils.random(1., 9.) * 100000) + "";
        var model = User.builder()
                .mobile(phoneNumber)
                .id(u.getId())
                .password(verifyCode)
                .build();

        redis.boundValueOps(RESET_PASSWORD_PREFIX + token).set(JSONUtil.toJson(model), 10, TimeUnit.MINUTES);
        Map<String, String> p = new HashMap<>();
        p.put("code", verifyCode);
        smsService.sendSms(phoneNumber, "SMS_159530057", p);
        return token;
    }

    @Override
    public void verifyResetPassword(String smsToken, String verifyCode, String newPassword) {
        String modelString = redis.boundValueOps(RESET_PASSWORD_PREFIX + smsToken).get();
        if (modelString == null) {
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "验证码无效或已过期");
        }


        var model = JSONUtil.parseJson(modelString, User.class);
        if (!model.getPassword().equals(verifyCode))
            throw new ServiceException(ResultCode.ERROR_TOAST_ERR, "验证码无效或已过期");
        redis.delete(RESET_PASSWORD_PREFIX + smsToken);

        userMapper.modifyUser(User.builder()
                .id(model.getId())
                .password(SignConfig.getHash(newPassword, model.getId()))
                .build());

    }

    @Override
    public void earnVip(User user, int days) {
        var startTime = new Date();
        boolean isPermanent = false;
        if (user.getVipType() == User.VipType.PAYED) { // 已经是会员, 则补一段会员, 调整开始时间
            if (user.getVipValidation() != null) {
                startTime = user.getVipValidation();
            }
        } else if (user.getVipType() == User.VipType.ORDINARY) { // 普通用户则升级成会员
            user.setVipType(User.VipType.PAYED);
            user.setVipSubType(User.VipSubType.TEMP);
            user.setVipStartTime(startTime);
        }

        Calendar c = Calendar.getInstance();
        c.setTime(startTime);
        c.add(Calendar.DAY_OF_MONTH, Math.min(days, 365000)); // 最多加1000年, 免得爆掉db
        var endTime = c.getTime();
        user.setVipValidation(endTime);
        if (days == Integer.MAX_VALUE) {
            user.updateToPermanentVip();
            isPermanent = true;
        }

        earnVip(user, isPermanent, startTime, endTime);
    }

    @Override
    public void earnVip(User user, boolean isPermanent, Date startDate, Date endDate) {

        userVipFlowMapper.insertFlow(user.getId(), UserVipFlowMapper.Type.PAYED, isPermanent ? UserVipFlowMapper.SubType.PERMANENT : UserVipFlowMapper.SubType.TEMP, startDate, endDate);
        modifyUser(user);

        mPosService.syncUserFeeRate(user);
    }

    @Override
    public void earnVip(User user, boolean isPermanent, Date endDate) {
        earnVip(user, isPermanent, new Date(), endDate);
    }

    @Override
    public void earnPrimeVip(User user, int days) {
        var startTime = new Date();
        if (user.getVipType() == User.VipType.PRIME) {
            if (user.getPrimeVipValidation() != null) {
                startTime = user.getPrimeVipValidation();
            }
        } else {
            user.setVipType(User.VipType.PRIME);
        }

        Calendar c = Calendar.getInstance();
        c.setTime(startTime);
        c.add(Calendar.DAY_OF_MONTH, Math.min(days, 365000)); // 最多加1000年, 免得爆掉db
        var endTime = c.getTime();

        user.setPrimeVipValidation(endTime);

        userVipFlowMapper.insertFlow(user.getId(), UserVipFlowMapper.Type.PRIME, null, startTime, endTime);
        modifyUser(user);

        mPosService.syncUserFeeRate(user);
    }

    @Override
    public boolean isVipInTime(User user, Date moment) {
        if (moment == null) {
            throw new ServiceException(ResultCode.ERROR_INTERNAL_DATA_CODE, "should not pass null moment", new RuntimeException());
        }
        var result = userVipFlowMapper.isVipInTime(user.getId(), UserVipFlowMapper.Type.PAYED, null, moment);
        return result != null;
    }

    @Override
    public boolean isPermanentVipInTime(User user, Date moment) {
        if (moment == null) {
            throw new ServiceException(ResultCode.ERROR_INTERNAL_DATA_CODE, "should not pass null moment", new RuntimeException());
        }
        var result = userVipFlowMapper.isVipInTime(user.getId(), UserVipFlowMapper.Type.PAYED, UserVipFlowMapper.SubType.PERMANENT, moment);
        return result != null;
    }

    @Override
    public boolean isPrimeVipInTime(User user, Date moment) {
        var result = userVipFlowMapper.isVipInTime(user.getId(), UserVipFlowMapper.Type.PRIME, null, moment);
        return result != null;
    }

    @Override
    public void checkVipValidation(String userId) {
        try {
            var user = getUser(userId);
            log.info("userVipType:" + user.getVipType() + " subType:" + user.getVipSubType());
            var now = new Date();
            var vipStatus = userVipFlowMapper.isVipInTime(user.getId(), UserVipFlowMapper.Type.PAYED, null, now);
            var primeStatus = userVipFlowMapper.isVipInTime(user.getId(), UserVipFlowMapper.Type.PRIME, null, now);

            // 仅对比vipFlow和当前状态不一致的情况, 避免性能问题
            if (primeStatus != null) {
                if (user.getVipType() != User.VipType.PRIME) {
                    user.setVipType(User.VipType.PRIME);
                    var endTimeFlow = userVipFlowMapper.latestInvalidVipFlow(user.getId(), UserVipFlowMapper.Type.PRIME, null);
                    user.setPrimeVipValidation(endTimeFlow.getEndTime());

                    modifyUser(user);
                    mPosService.syncUserFeeRate(user);
                }
            } else if (vipStatus != null) {
                if (user.getVipType() != User.VipType.PAYED || user.getVipSubType() != vipStatus.getSubType()) {
                    // 皇冠vip必定是付费vip, 可以直接降级
                    user.setVipType(vipStatus.getType());
                    user.setVipSubType(vipStatus.getSubType());
                    var endTimeFlow = userVipFlowMapper.latestInvalidVipFlow(user.getId(), UserVipFlowMapper.Type.PAYED, null);
                    user.setVipValidation(endTimeFlow.getEndTime());

                    modifyUser(user);
                    mPosService.syncUserFeeRate(user);

                }
                
            } else if (vipStatus == null && primeStatus == null && user.getVipType() != User.VipType.ORDINARY){
                user.setVipType(User.VipType.ORDINARY);
                modifyUser(user);
                mPosService.syncUserFeeRate(user);

            }
//            if (user.getVipType() != User.VipType.ORDINARY) {
//                if (user.getVipType() == User.VipType.PAYED && user.getVipSubType() != User.VipSubType.PERMANENT) {
//                    // 非永久vip, 检查有效期
//                    if (!isVipInTime(user, new Date())) {
//                        user.setVipType(User.VipType.ORDINARY);
//                        modifyUser(user);
//                    }
//                } else if (user.getVipType() == User.VipType.PRIME) {
//                    if (!isPrimeVipInTime(user, new Date())) {
//                        user.setVipType(User.VipType.PAYED); // 皇冠vip必定是付费vip, 可以直接降级
//                        modifyUser(user);
//                    }
//                }
//            } else {
//
//            }

        } catch (Throwable t) {
            log.error("checkvipvalid err", t);
            // 不需要处理, 理论上也不会走到这个分支
        }


    }

    @Override
    public void setUserActive(String userId) {
        userMapper.updateUserActiveTime(userId);
    }

    @Override
    public Long isActive(String userId, Date startTime, Date endTime) {
        return userMapper.isActiveUserInTimeRange(userId, startTime, endTime);
    }

    @Override
    public Long getVipStartInRangeCount(List<String> userIds, Date startTime, Date endTime) {
        if (userIds.size() == 0) {
            return 0L;
        }
        return userMapper.getVipStartInRangeCount(userIds, startTime, endTime);
    }

    @Override
    public int isVipConsumeOccurredInTimeRange(String userId, Date startTime, Date endTime) {
        return userVipFlowMapper.isOccurTimeInRange(userId, UserVipFlowMapper.Type.PAYED, UserVipFlowMapper.SubType.PERMANENT, startTime, endTime) != null ? 1 : 0;
    }

    @Override
    public int isPrimeVipConsumeOccurredInTimeRange(String userId, Date startTime, Date endTime) {
        return userVipFlowMapper.isOccurTimeInRange(userId, UserVipFlowMapper.Type.PRIME, null, startTime, endTime) != null ? 1 : 0;
    }

    @Override
    public ThirdAuthVo getThirdAuthToken(String appId, String authCode) {
        String key = THIRD_CODE_PREFIX + appId + "-" + authCode;
        if (redis.hasKey(key)) {
            var userId = redis.boundValueOps(key).get();
            var user = getUser(userId);
            redis.delete(key);
            var token = UUID.randomUUID().toString().replace("-", "") + UUID.randomUUID().toString().replace("-", "");
            redis.boundValueOps(THIRD_AUTH_TOKEN_PREFIX + appId + "-" + token).set(userId);

            return ThirdAuthVo.builder()
                    .token(token)
                    .userId(userId)
                    .user(User.builder()
                            .id(user.getId())
                            .avatar(TextUtils.isEmpty(user.getAvatar()) ? null : ossServiceImpl.signUrl(user.getAvatar()))
                            .vipType(user.getVipType())
                            .build())
                    .build();
        }
        throw new ServiceException(ResultCode.ERROR_TOKEN_INVALID_CODE, "auth code invalid");
    }

    @Override
    public String genThirdAuthCode(String appId, String userId) {
        var code = UUID.randomUUID().toString().replace("-", "");
        redis.boundValueOps(THIRD_CODE_PREFIX + appId + "-" + code).set(userId, 5, TimeUnit.MINUTES);
        return code;
    }

    @Override
    public User userByThirdAuthToken(String appId, String token) {
        var key = THIRD_AUTH_TOKEN_PREFIX + appId + "-" + token;
        if (!redis.hasKey(key)) {
            throw new ServiceException(ResultCode.ERROR_DB_CODE, "token invalid");
        }

        var userId = redis.boundValueOps(key).get();
        return getUser(userId);
    }

    @Override
    public Long certificatingFollowerCount(String invitorId, Date start, Date end) {
        return userIdentifyMapper.certificatingFollowerCountInRange(invitorId, start, end);
    }
}
